package com.topfun.utils;

import com.google.gson.annotations.SerializedName;
import com.topfun.dao.MarketRecordDao;
import com.topfun.dao.PaymentDao;
import com.topfun.dao.UserDao;
import com.topfun.vo.MarketRecord;
import com.topfun.vo.User;
import com.topfun.vo.payment.ChargeOrder;
import com.topfun.vo.payment.WeChatOrder;
import com.topfun.vo.payment.WeChatOrderResult;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.util.TreeMap;

/**
 * Created by huangda on 2018-08-21.
 *
 */
@SuppressWarnings("WeakerAccess")
public class WeChatPayUtil {
    // ===========================================================
    // Constants
    // ===========================================================
    private static final Logger logger = LoggerFactory.getLogger(WeChatPayUtil.class);

    // ===========================================================
    // Fields
    // ===========================================================


    // ===========================================================
    // Constructors
    // ===========================================================


    // ===========================================================
    // Getter &amp; Setter
    // ===========================================================


    // ===========================================================
    // Methods for/from SuperClass/Interfaces
    // ===========================================================


    // ===========================================================
    // Methods
    // ===========================================================
    public static WeChatOrderResult unifiedOrder(WeChatOrder pOrder){
        OkHttpClient      client          = new OkHttpClient();
        MediaType MEDIA_TYPE_XML = MediaType.parse("application/xml");
        String    postBody        = generateWeChatOrderXML(pOrder);
        logger.debug(String.format("postBody %s", postBody));
        long a = System.currentTimeMillis();
        Request request = new Request.Builder()
                .url("https://api.mch.weixin.qq.com/pay/unifiedorder")
                .post(RequestBody.create(MEDIA_TYPE_XML, postBody))
                .build();

        try {
            Response response = client.newCall(request).execute();
            if (!response.isSuccessful()) {
                logger.error(String.format("WeChat unified order fail : %s", response.body().string()));
                response.close();
                return null;
            }
            long b = System.currentTimeMillis();
            System.out.println(String.format("request https://api.mch.weixin.qq.com/pay/unifiedorder is %d s", b - a));

            String body = response.body().string();
            logger.debug(String.format("WeChat unified order return :\n%s", body));

            return parseUnifiedOrderResult(pOrder, body);
        } catch (IOException e) {
            logger.error(String.format("WeChat unified order error", e));
        }
        return null;
    }

    private static String generateWeChatOrderXML(WeChatOrder pOrder){
        StringBuilder orderXML = new StringBuilder();
        orderXML.append("<xml>");
        orderXML.append("<appid>").append(pOrder.appid).append("</appid>");
        orderXML.append("<body><![CDATA[").append(pOrder.body).append("]]></body>");
        orderXML.append("<limit_pay>").append(pOrder.limit_pay).append("</limit_pay>");
        orderXML.append("<mch_id>").append(pOrder.mch_id).append("</mch_id>");
        orderXML.append("<nonce_str>").append(pOrder.nonce_str).append("</nonce_str>");
        orderXML.append("<notify_url>").append(pOrder.notify_url).append("</notify_url>");
        if("JSAPI".equals(pOrder.trade_type)){
            orderXML.append("<openid>").append(pOrder.openid).append("</openid>");
        }
        orderXML.append("<out_trade_no>").append(pOrder.out_trade_no).append("</out_trade_no>");
        orderXML.append("<spbill_create_ip>").append(pOrder.spbill_create_ip).append("</spbill_create_ip>");
        orderXML.append("<total_fee>").append(pOrder.total_fee).append("</total_fee>");
        orderXML.append("<trade_type>").append(pOrder.trade_type).append("</trade_type>");
        orderXML.append("<sign>").append(sign(pOrder)).append("</sign>");
        orderXML.append("</xml>");
        return orderXML.toString();
    }

    private static WeChatOrderResult parseUnifiedOrderResult(WeChatOrder pOrder, String pBody){
        if(pBody != null && pBody.contains("<return_code>")){
            WeChatOrderResult result = new WeChatOrderResult();
            result.return_code  = StringUtil.getXMLValue(pBody, "return_code");  // 返回状态码，SUCCESS/FAIL
            result.return_msg   = StringUtil.getXMLValue(pBody, "return_msg");   // 返回信息
            if(!"SUCCESS".equals(result.return_code)){
                logger.error(String.format("WeChat unified order return error : %s", result.return_msg));
                return null;
            }
            result.result_code  = StringUtil.getXMLValue(pBody, "result_code");  // 业务结果，SUCCESS/FAIL
            result.err_code     = StringUtil.getXMLValue(pBody, "err_code");     // 错误代码
            result.err_code_des = StringUtil.getXMLValue(pBody, "err_code_des"); // 错误代码描述
            if(!"SUCCESS".equals(result.result_code)){
                logger.error(String.format("WeChat unified order return fail (%d) %s", result.err_code, result.err_code_des));
                return null;
            }
            result.appid        = StringUtil.getXMLValue(pBody, "appid");        // 公众账号ID
            result.mch_id       = StringUtil.getXMLValue(pBody, "mch_id");       // 商户号
            result.device_info  = StringUtil.getXMLValue(pBody, "device_info");  // 设备号，非必须
            result.nonce_str    = StringUtil.getXMLValue(pBody, "nonce_str");    // 随机字符串
            result.trade_type   = StringUtil.getXMLValue(pBody, "trade_type");   // 交易类型
            result.prepay_id    = StringUtil.getXMLValue(pBody, "prepay_id");    // 预支付交易会话标识
            result.code_url     = StringUtil.getXMLValue(pBody, "code_url");     // 二维码链接
            result.sign         = StringUtil.getXMLValue(pBody, "sign");         // 签名
            if(checkSign(result, pOrder.mchSecret)){
                return result;
            }
        }
        return null;
    }

    /**
     * 计算微信下单签名
     * params 应当与 generateWeChatOrderXML 中发送参数一致
     * @param pOrder 微信订单
     * @return 签名
     */
    private static String sign(WeChatOrder pOrder){
        TreeMap<String, String> params = new TreeMap<>();
        params.put("appid", pOrder.appid);
        params.put("body", pOrder.body);
        params.put("mch_id", pOrder.mch_id);
        params.put("nonce_str", pOrder.nonce_str);
        params.put("notify_url", pOrder.notify_url);
        params.put("out_trade_no", pOrder.out_trade_no);
        params.put("spbill_create_ip", pOrder.spbill_create_ip);
        params.put("total_fee", String.valueOf(pOrder.total_fee));
        params.put("trade_type", pOrder.trade_type);
        params.put("limit_pay", pOrder.limit_pay);
        if("JSAPI".equals(pOrder.trade_type)){
            params.put("openid", pOrder.openid);
        }
        String tail = "key=".concat(pOrder.mchSecret);
        return StringUtil.makeSignCommon(params, "&", "", tail).toUpperCase();
    }

    /**
     * 微信公众号支付签名校验
     * @param pResult  预下单成功之后返回的信息
     * @param pSecret 商户秘钥
     * @return
     */
    private static boolean checkSign(WeChatOrderResult pResult, String pSecret){
        if(pResult != null) {
            TreeMap<String, String> params = new TreeMap<>();
            params.put("return_code", pResult.return_code); // 返回状态码，SUCCESS/FAIL
            params.put("return_msg", pResult.return_msg); // 返回信息
            params.put("result_code", pResult.result_code); // 业务结果，SUCCESS/FAIL
            params.put("err_code", pResult.err_code); // 错误代码
            params.put("err_code_des", pResult.err_code_des); // 错误代码描述
            params.put("appid", pResult.appid); // 公众账号ID
            params.put("mch_id", pResult.mch_id); // 商户号
            params.put("device_info", pResult.device_info); // 设备号，非必须
            params.put("nonce_str", pResult.nonce_str); // 随机字符串
            params.put("trade_type", pResult.trade_type); // 交易类型
            params.put("prepay_id", pResult.prepay_id); // 预支付交易会话标识
            params.put("code_url", pResult.code_url); // 二维码链接
            String tail = "key=".concat(pSecret);
            String sign = StringUtil.makeSignCommon(params, "&", "", tail).toUpperCase();
            logger.debug(String.format("REQ-Sign: %s", pResult.sign));
            logger.debug(String.format("LOC-Sign: %s", sign));
            return sign.equals(pResult.sign);
        }
        return false;
    }

    /**
     * 预下单order
     * @param order 充值订单
     * @param wxOrder 微信预下单订单
     * @return code
     */
    public static Code buildWechatOrder(ChargeOrder order, WeChatOrder wxOrder){
        wxOrder.appid = Constants.WeChat_App_ID;
        wxOrder.mchSecret = Constants.WeChat_Mch_Secret;
        wxOrder.mch_id = Constants.Wechat_Mch_Id;
        wxOrder.body = order.description;
        wxOrder.nonce_str = StringUtil.generateUUID();
        wxOrder.notify_url = Constants.Payment_Notify_URL_Wechat;
        wxOrder.out_trade_no = order.orderId;
        wxOrder.spbill_create_ip = StringUtil.isEmpty(order.ip) ? "0.0.0.0" : order.ip;
        wxOrder.total_fee = order.amount;
        wxOrder.trade_type = "JSAPI";//微信公众号支付
        // openid : String(128) trade_type=JSAPI时（即公众号支付），此参数必传，此参数为微信用户在商户对应appid下的唯一标识。
        if("JSAPI".equals(wxOrder.trade_type)){
            wxOrder.openid = order.openId;
            if(StringUtil.isEmpty(wxOrder.openid)){
                return Code.ERR_MISS_PARAMETERS;
            }
        }
        return Code.SUCCESS;
    }

    /**
     * 构建支付的信息payInfo
     * @param pResult 预下单结果
     * @return str
     */
    public static String generatePayInfo(WeChatOrderResult pResult){
       if("JSAPI".equals(pResult.trade_type)){
            // appId     String(16)  公众号id，商户注册具有支付权限的公众号成功后即可获得
            // timeStamp String(32)  时间戳
            // nonceStr  String(32)  随机字符串，不长于32位
            // package   String(128) 订单详情扩展字符串，统一下单接口返回的prepay_id参数值，提交格式如：prepay_id=***
            // signType  String(32)  签名方式，暂支持MD5
            // paySign   String(64)  签名
            PayInfoJSAPI payInfoJSAPI = new PayInfoJSAPI();
            payInfoJSAPI.appId = pResult.appid;
            payInfoJSAPI.timeStamp = String.valueOf(System.currentTimeMillis()/1000);
            payInfoJSAPI.nonceStr = StringUtil.generateUUID();
            payInfoJSAPI.packageName = String.format("prepay_id=%s", pResult.prepay_id);
            payInfoJSAPI.signType = "MD5";
            payInfoJSAPI.paySign = MD5.getMD5(
                    String.format("appId=%s&nonceStr=%s&package=%s&signType=%s&timeStamp=%s&key=%s",
                            payInfoJSAPI.appId, payInfoJSAPI.nonceStr, payInfoJSAPI.packageName,
                            payInfoJSAPI.signType, payInfoJSAPI.timeStamp,
                            Constants.WeChat_Mch_Secret
                    )
            ).toUpperCase();
            return GsonUtil.toJson(payInfoJSAPI);
        }
        return "";
    }

    /**
     * 微信支付notify响应
     * @param isSuccess 是否成功
     * @return String
     */
    public static String response(boolean isSuccess) {
        if(isSuccess){
            return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
        }else{
            return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[FAIL]]></return_msg></xml>";
        }
    }

    /**
     * 微信支付notify数据校验处理
     * @param request 参数
     * @return Code
     */
    public static Code parse(HttpServletRequest request){
        String body = "";
        try {
            InputStream inputStream = request.getInputStream();
            // 取HTTP请求流长度
            int size = request.getContentLength();
            // 用于缓存每次读取的数据
            byte[] buffer = new byte[size];
            // 用于存放结果的数组
            byte[] dataByte = new byte[size];
            int count = 0;
            int rbyte = 0;
            // 循环读取
            while (count < size) {
                // 每次实际读取长度存于rbyte中
                rbyte = inputStream.read(buffer);
                for(int i=0;i<rbyte;i++) {
                    dataByte[count + i] = buffer[i];
                }
                count += rbyte;
            }
            body = new String(dataByte, "UTF-8");
        } catch (IOException e) {
            e.printStackTrace();
        }
        if(body != null){
            WeChatNotify notify = new WeChatNotify();
            // 请求状态码
            notify.return_code = StringUtil.getXMLValue(body, "return_code");
            notify.return_msg = StringUtil.getXMLValue(body, "return_msg");
            if(!"SUCCESS".equals(notify.return_code)){
                logger.error(String.format("Invalid WeChat notify \n%s", body));
                return Code.ERR_INVALID_NOTIFY;
            }
            // 业务状态码
            notify.result_code = StringUtil.getXMLValue(body, "result_code");
            notify.err_code = StringUtil.getXMLValue(body, "err_code");
            notify.err_code_des = StringUtil.getXMLValue(body, "err_code_des");
            if(!"SUCCESS".equals(notify.return_code)){
                logger.error(String.format("Fail WeChat notify \n%s", body));
            }
            // 通知数据
            notify.appid = StringUtil.getXMLValue(body, "appid");
            notify.bank_type = StringUtil.getXMLValue(body, "bank_type");
            notify.fee_type = StringUtil.getXMLValue(body, "fee_type");
            notify.mch_id = StringUtil.getXMLValue(body, "mch_id");
            notify.nonce_str = StringUtil.getXMLValue(body, "nonce_str");
            notify.openid = StringUtil.getXMLValue(body, "openid");
            notify.out_trade_no = StringUtil.getXMLValue(body, "out_trade_no");
            notify.sign = StringUtil.getXMLValue(body, "sign");
            notify.time_end = StringUtil.getXMLValue(body, "time_end");
            notify.trade_type = StringUtil.getXMLValue(body, "trade_type");
            notify.transaction_id = StringUtil.getXMLValue(body, "transaction_id");
            notify.device_info = StringUtil.getXMLValue(body, "device_info");
            notify.sign_type = StringUtil.getXMLValue(body, "sign_type");
            notify.total_fee = StringUtil.getXMLValue(body, "total_fee");
            notify.cash_fee = StringUtil.getXMLValue(body, "cash_fee");
            notify.is_subscribe = StringUtil.getXMLValue(body, "is_subscribe");
            notify.settlement_total_fee = StringUtil.getXMLValue(body, "settlement_total_fee");
            notify.cash_fee_type = StringUtil.getXMLValue(body, "cash_fee_type");
            notify.coupon_fee = StringUtil.getXMLValue(body, "coupon_fee");
            notify.coupon_count = StringUtil.getXMLValue(body, "coupon_count");
            notify.attach = StringUtil.getXMLValue(body, "attach");

            if(!StringUtil.isEmpty(notify.coupon_count)){
                try {
                    int count = Integer.parseInt(notify.coupon_count);
                    notify.coupon_fees = new String[count];
                    notify.coupon_ids = new String[count];
                    notify.coupon_types = new String[count];
                    for(int i=0; i<count; i++){
                        notify.coupon_fees[i] = StringUtil.getXMLValue(body, "coupon_fee_" + (i));
                        notify.coupon_ids[i] = StringUtil.getXMLValue(body, "coupon_id_" + (i));
                        notify.coupon_types[i] = StringUtil.getXMLValue(body, "coupon_type_" + (i));
                    }
                } catch (NumberFormatException ignore) {
                }
            }

            if(checkNotifySign(notify)){
                ChargeOrder chargeOrder = PaymentDao.queryChargeOrderByOrderId(notify.out_trade_no);
                if(null == chargeOrder){ //查询订单是否存在
                    logger.error(String.format("WeChat notify out_trade_no(orderId: %s) get ChargeOrder is null !", notify.out_trade_no));
                    return Code.ERR_INVALID_NOTIFY;
                }
                if(chargeOrder.status == ChargeOrder.Status_Apply){ //如果是申请状态
                    if(chargeOrder.amount != Integer.parseInt(notify.total_fee)){//判断订单金额是否一致
                        logger.error(String.format("WeChat notify total_fee : %s not equals ChargeOrder amount: %d !", notify.total_fee,chargeOrder.amount));
                        return Code.ERR_INVALID_NOTIFY;
                    }
                    int status = "SUCCESS".equals(notify.result_code) ? ChargeOrder.Status_Success : ChargeOrder.Status_Fail;
                    String remark = "SUCCESS".equals(notify.result_code) ? "SUCCESS" : String.format("%s\t%s",notify.err_code,notify.err_code_des);
                    PaymentDao.updateChargeOrderStatus(notify.out_trade_no,status,remark);
                    User user = UserDao.queryUserByUserId(chargeOrder.userId);
                    UserDao.updateUserAmount(user.userId,user.amount+ (ConfigLoadUtil.isDebug()?chargeOrder.amount / 10:chargeOrder.amount / 10));//发放乐豆
                    MarketRecord    marketRecord = new MarketRecord(user,ConfigLoadUtil.isDebug()?chargeOrder.amount / 10:chargeOrder.amount / 10,"充值发放",MarketRecord.STATUS_IN,MarketRecord.TYPE_RECHARGE);
                    MarketRecordDao.insertMarketRecord(marketRecord);
                    return Code.SUCCESS;
                }
            }else{
                logger.error(String.format("WeChat sign check fail \n%s", body));
                return Code.ERR_INVALID_NOTIFY_SIGN;
            }
        }
        return Code.ERR_INVALID_NOTIFY;
    }

    /**
     * 微信支付notify签名校验
     * @param pNotify notify
     * @return boolean
     */
    private static boolean checkNotifySign(WeChatNotify pNotify){
        if(pNotify != null){
            TreeMap<String, String> params = new TreeMap<>();
            params.put("return_code", pNotify.return_code);
            params.put("return_msg", pNotify.return_msg);
            params.put("appid", pNotify.appid);
            params.put("mch_id", pNotify.mch_id);
            params.put("device_info", pNotify.device_info);
            params.put("nonce_str", pNotify.nonce_str);
            params.put("sign_type", pNotify.sign_type);
            params.put("result_code", pNotify.result_code);
            params.put("err_code", pNotify.err_code);
            params.put("err_code_des", pNotify.err_code_des);
            params.put("openid", pNotify.openid);
            params.put("trade_type", pNotify.trade_type);
            params.put("bank_type", pNotify.bank_type);
            params.put("total_fee", pNotify.total_fee);
            params.put("fee_type", pNotify.fee_type);
            params.put("cash_fee", pNotify.cash_fee);
            params.put("transaction_id", pNotify.transaction_id);
            params.put("out_trade_no", pNotify.out_trade_no);
            params.put("time_end", pNotify.time_end);
            params.put("is_subscribe", pNotify.is_subscribe);
            params.put("settlement_total_fee", pNotify.settlement_total_fee);
            params.put("cash_fee_type", pNotify.cash_fee_type);
            params.put("coupon_fee", pNotify.coupon_fee);
            params.put("coupon_count", pNotify.coupon_count);
            params.put("attach", pNotify.attach);
            if(pNotify.coupon_ids != null && pNotify.coupon_ids.length > 0){
                for(int i=0; i<pNotify.coupon_ids.length; i++){
                    params.put("coupon_id_" + i, pNotify.coupon_ids[i]);
                    params.put("coupon_type_" + i, pNotify.coupon_types[i]);
                    params.put("coupon_fee_" + i, pNotify.coupon_fees[i]);
                }
            }
            String tail = "key=".concat(Constants.WeChat_Mch_Secret);
            String sign = StringUtil.makeSignCommon(params, "&", "", tail).toUpperCase();
            logger.debug(String.format(" Local Sign %s", sign));
            logger.debug(String.format("Notify Sign %s", pNotify.sign));
            return sign.equals(pNotify.sign);
        }
        return false;
    }


  /*  public static void main(String[] args){
        WeChatOrder order = new WeChatOrder();
        order.appid = "wx46f6f8243c233e87";
        order.mchSecret = "1d5af88180a708f4dc265b90b620b8a9";
        order.mch_id = "1260774101";
        order.body = "10000金币";
        order.nonce_str = StringUtil.generateUUID();
        order.notify_url = "http://www.baidu.com";
        order.out_trade_no = "TEST"+System.currentTimeMillis();
        order.spbill_create_ip = "113.91.87.126";
        order.total_fee = 1;
        order.trade_type = "NATIVE";
        WeChatOrderResult result = unifiedOrder(order);
        LOG.dd("unified result : %s", result);
    }*/




    // ===========================================================
    // Inner and Anonymous Classes
    // ===========================================================
    private static final class PayInfoJSAPI{
        String appId = "";
        String timeStamp = "";
        String nonceStr  = "";
        @SerializedName("package")
        String packageName = "";
        String signType = "MD5";
        String paySign = "";
    }

    private static class WeChatNotify{
        String return_code; // 返回状态码，SUCCESS/FAIL
        String return_msg; // 返回信息
        String appid; // 公众账号ID
        String mch_id; // 商户号
        String device_info; // 微信支付分配的终端设备号，
        String nonce_str; // 随机字符串
        String sign; // 签名
        String sign_type; // 签名类型，目前支持HMAC-SHA256和MD5，默认为MD5
        String result_code; // 业务结果，SUCCESS/FAIL
        String err_code; // 错误代码
        String err_code_des; // 错误代码描述
        String openid; // 用户在商户appid下的唯一标识
        String trade_type; // 交易类型，JSAPI、NATIVE、APP
        String bank_type; // 采用字符串类型的银行标识
        String total_fee; // 订单总金额，单位为分
        String fee_type; // 货币类型，符合ISO4217标准的三位字母代码，默认人民币：CNY
        String cash_fee; // 现金支付金额订单现金支付金额
        String transaction_id; // 微信支付订单号
        String out_trade_no; // 商户订单号
        String time_end; // 支付完成时间，格式为yyyyMMddHHmmss
        String is_subscribe; // 用户是否关注公众账号，Y-关注，N-未关注，仅在公众账号类型支付有效
        String settlement_total_fee; // 应结订单金额=订单金额-非充值代金券金额，应结订单金额<=订单金额。
        String cash_fee_type; // 现金支付货币类型，符合ISO4217标准的三位字母代码，默认人民币：CNY
        String coupon_fee; // 总代金券金额,代金券金额<=订单金额，订单金额-代金券金额=现金支付金额，详见支付金额
        String coupon_count; // 代金券使用数量
        String[] coupon_types; // 代金券类型，CASH-充值代金券 NO_CASH-非充值代金券
        String[] coupon_ids; // 代金券ID
        String[] coupon_fees; // 单个代金券支付金额
        String attach; // 商家数据包

    }
}
